import java.util.ArrayList;

public class IOProgram
{
    public final String MANUAL_ACTIVATION = "[MANUAL ACTIVATION]";

    private final String OLineTooLongMessage = "[too many TextFields excited, text of further ones not listed here any more]";

    private MainWindow.GUIElements GUIElements;

    private String IOProgramString;

    private ArrayList<String> ILines; // each line (completed by line break) the USER entered
    private ArrayList<String> OLines; // each line the SYSTEM added (system output always surrounded by braces [], removed from IOTextArea each time the simulation starts)

    private int IOProgramLineExecutedCurrent; // which element in ILines and OLines (they are in sync)

    private Simulation Simulation = null; // MUST be null to be initialized with a new Simulation instance

    public void AppendToOLine(int OLineIndex, String AppendString)
    { // note to add AppendString to stuff in braces [] in IOTextArea

        if (AppendString.length() >= 1)
        {
            if (OLineIndex >= 0 && OLineIndex < OLines.size())
            {
                String OLineContent = OLines.get(OLineIndex); // returns the REFERENCE to OLine string in memory
                if (OLineContent.length() < 1024)
                {
                    OLineContent = OLineContent + (OLineContent.length() > 0 ? "/" : "") + AppendString; // new String has a new memory location (new reference) (tested)...
                    OLines.set(OLineIndex, OLineContent); // ...so we NEED to update reference in ArrayList
                }
                else if (!(OLineContent.endsWith(OLineTooLongMessage)))
                {
                    OLineContent = OLineContent + (OLineContent.length() > 0 ? "/" : "") + OLineTooLongMessage;
                    OLines.set(OLineIndex, OLineContent);
                }
            }
        }
        else
            System.out.println("warning in AppendToOLine(): AppendString is \"\"!");
    }

    private String AssembleIOProgramDisplayString(ArrayList<String> ILines, ArrayList<String> OLines, int IOProgramLineExecutedCurrent)
    { // combine all ILines and OLines, result of this function should be displayed in IOTextArea

        int IOProgramDisplayStringLengthEstimated = (ILines.size() * (50 + 2)); // 50 chars per line plus newlines (probably more than required, but no problem)
        StringBuilder IOProgramDisplayString = new StringBuilder(IOProgramDisplayStringLengthEstimated);

        // everything in braces [] is system output, it will be removed
        // when re-starting simulation!

        for (int i = 0; i < ILines.size(); i++)
        {
            IOProgramDisplayString.append(ILines.get(i));
            if (OLines.get(i).length() >= 1)
            {
                IOProgramDisplayString.append("[");
                IOProgramDisplayString.append(OLines.get(i));
                IOProgramDisplayString.append("]");
            }
            if (i == IOProgramLineExecutedCurrent)
            {
                IOProgramDisplayString.append("[<==]");
            }
            if (i < (ILines.size() - 1)) // don't append more and more blank lines at end (happened, tested)
                IOProgramDisplayString.append("\r\n");
        }

        return IOProgramDisplayString.toString();
    }

    public void ClearOLines()
    { // delete stuff displayed in braces [] in IOTextArea

        for (int i = 0; i < OLines.size(); i++)
            OLines.set(i, ""); // no matter what we did with existing String, Java would create a new String anyway, so throw away old String
    }

    public void DoRedraw()
    {
        String IOProgramDisplayString = GetIOProgramDisplayString();

        // NOTE: seems as if JTextArea can't handle \r\n correctly, at least not on
        // my OS (Linux Mint), make sure there's exclusively \n, not \r\n and not \r!

        GUIElements.GetIOPanel().GetIOTextArea().setText(IOProgramDisplayString.replaceAll("\r\n", "\n").replaceAll("\r", "\n")); // must be updated permanently, as content changes with each new IOTextArea line having been processed
        GUIElements.GetMainWindow().DoRedraw(); // simulation NEEDS this (tested)
    }

    public String GetILine(int ILineIndex)
    { // just get an array element

        if (ILineIndex >= 0 && ILineIndex < ILines.size())
            return ILines.get(ILineIndex);
        else
            return null;
    }

    private String GetIOProgramDisplayString()
    { // assemble user's original input and the output generated by the system during simulation

        String IOProgramDisplayString = AssembleIOProgramDisplayString(ILines, OLines, GetIOProgramLineExecutedCurrent());

        return IOProgramDisplayString;
    }

    public int GetIOProgramLineExecutedCurrent()
    {
        return IOProgramLineExecutedCurrent;
    }

    public String GetIOProgramString()
    {
        if (IOProgramString == null)
            return "";
        else
            return IOProgramString;
    }

    public boolean IsExecuted()
    {
        if (Simulation != null)
            return Simulation.IsSimulationRunning();
        else
            return false;
    }

    public void PauseExecution()
    {
        if (Simulation != null)
            Simulation.PauseSimulation();
    }

    public void ReactOnTextFieldActivation(TextField TextField)
    {
        AppendToOLine(GetIOProgramLineExecutedCurrent(), TextField.GetText());
    }

    public void ResumeExecution()
    {
        if (Simulation != null)
            Simulation.ResumeSimulation();
    }

    public void SetIOProgramLineExecutedCurrent(int IOProgramLineExecutedCurrentNew)
    { // system uses this to permanently set (i.e. internally mark) index of currently executed line (from IOTextArea), during simulation

        if (IOProgramLineExecutedCurrentNew >= 0 && IOProgramLineExecutedCurrentNew < ILines.size())
            IOProgramLineExecutedCurrent = IOProgramLineExecutedCurrentNew;
        // else // don't print error message, Simulation detects end of IO program when GetIOProgramLineExecutedCurrent() returns "error"
        // System.out.println("error in IOProgram.SetIOProgramLineExecutedCurrent(): passed value not useful!");
    }

    public void SetIOProgramString(String IOProgramStringNew)
    {
        if (IOProgramStringNew == null)
            IOProgramStringNew = "";

        if (IOProgramStringNew.length() > 1000000)
        {
            System.out.println("warning in IOProgram.SetIOProgramString(): passed value not useful!");
            IOProgramStringNew = IOProgramStringNew.substring(0, 1000000) + "[...]";
        }

        IOProgramString = IOProgramStringNew;

        ILines = StringToLines(IOProgramString, null);
        OLines = StringToLines(IOProgramString, "");

        IOProgramLineExecutedCurrent = 0; // reset this as simulation could be running (currently supported by system)
    }

    public void SetTickLostCount(int SetTickLostCountNew)
    { // the lost (i.e. actually delayed) tick count is currently just shown to the user to get an impression of CPU-load, just informative

        if (SetTickLostCountNew >= 0)
        {
            GUIElements.GetMainWindow().GetTitleBarInfo().RemoveInfoTextContaining(" ticks delayed"); // "ticks LOST" could confuse user, because ALL IO program lines are worked up, it just might take longer
            GUIElements.GetMainWindow().GetTitleBarInfo().AddInfoText(SetTickLostCountNew + " ticks delayed", true, false, false, false);
        }
        else
            System.out.println("error in IOProgram.SetTickLostCount(): passed value not useful!");
    }

    public void StartExecution(MainWindow.GUIElements GUIElements, ObjectStorage ObjectStorage, boolean ManualActivationOnly)
    {
        this.GUIElements = GUIElements;

        String IOProgramRaw = GUIElements.GetIOPanel().GetIOTextArea().getText(); // notice it might still contain system output in braces, e.g. [<==], from last simulation execution
        StringBuilder IOProgramOriginal = new StringBuilder(IOProgramRaw.length());

        if (ManualActivationOnly)
            IOProgramOriginal.append(MANUAL_ACTIVATION); // Simulation code will recognize this magic string and know the IO program is not to be executed, user can only activate DrawObjects using the space bar

        // remove all stuff in braces [] (former system output) and write result to IOProgramOriginal
        int InBracesCount = 0;
        for (int i = 0; i < IOProgramRaw.length(); i++)
        {
            if (IOProgramRaw.charAt(i) == '[')
                InBracesCount++;

            if (InBracesCount == 0)
                IOProgramOriginal.append(IOProgramRaw.charAt(i));

            if (IOProgramRaw.charAt(i) == ']')
                InBracesCount--;
        }

        // reset to the state the user has originally entered
        SetIOProgramString(IOProgramOriginal.toString());

        if (ManualActivationOnly == false)
        {
            // NOTE: F12 ALWAYS resets state, F11 never - so the user
            // can run an IO program, stop it and then do "experiments"
            // with the current state (e.g. LearningNeuron strengths);
            // if the user wants to reset state manually, he/she must
            // (shortly) start and stop an F12-simulation.

            ObjectStorage.ResetStateForAllObjects();
        }

        if (Simulation == null)
        {
            // NOTE: don't recreate Simulation instances, this lead to the
            // situation that multiple simulations were running if pressing
            // F12 again and again ("delayed ticks" in TitleBarInfo jumped
            // back and forth).

            Simulation = new Simulation(GUIElements, this, GUIElements.GetTicker(), ObjectStorage);
        }
        Simulation.StartSimulation();
    }

    public void StopExecution()
    {
        if (Simulation != null)
            Simulation.StopSimulation();
    }

    private ArrayList<String> StringToLines(String S, String LinesFixedStringOrNull)
    {
        ArrayList<String> Lines = new ArrayList<String>();

        S = S + "\r\n\01"; // we must append anything to avoid that S.split(...) removes empty lines at end of S (tested)

        S = S.replaceAll("\r\n", "\n"); // \r\n should be common on Windows systems
        S = S.replaceAll("\r", ""); // for some reasons, in tests just \r came up, don't know exactly why, just remove it (it could happen at any time that the user pasted just \r from somewhere...)
        String[] SLines = S.split("\n");

        for (int i = 0; i < SLines.length - 1; i++)
            if (LinesFixedStringOrNull == null)
                Lines.add(SLines[i]);
            else
                Lines.add(LinesFixedStringOrNull);

        return Lines;
    }
}
